package com.uusoft.core.utils.security;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import com.uusoft.core.utils.Constant;
import com.uusoft.core.utils.IOUtils;
import com.uusoft.core.utils.PropertiesParser;
import com.uusoft.core.utils.StringUtils;
import com.uusoft.core.utils.log.LogFactory;
import com.uusoft.core.utils.log.USoftLogger;

/**
 * 加密、解密工具类，本类采用RSA加密算法
 * 
 * @author shisheng.lian
 * 
 */
public class EncryptUtils {
	public static final String DEFAULT_KEY_ALGORITHM = "RSA";

	private static final String DEFAULT_PRIVATE_KEY_STRING = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJhuJzKUbDW1sNVN+rDk157THqwbYJl9TsJNon2C8VEiReu2CV5QbOUaR7mtTDkprkKDGg5gd4s9UAEwUQDh7gci84XD5e1HbCERKjN1h6xFe7HOSc4OoG3DDElKo2E5Y0QDnDRlsp/Xp79PmvDBeNwHimftomC/ZCejIV98nT6XAgMBAAECgYA6SUSrzp1iHEu2BM9rfNYGYWxFG2Lgzk2jdtQ/ciacg6KA82a5rb82C8qJcX7IDOwZetAn1zNfBt7k1XjkAYBmmx+jAp0vwufoPIrkF+bvx8hIKjP6OiFTe9VOnut3uM0VdC12Tb3tb8cisq4Auem+W4PvNQ1VeuTdDbwNI0bXgQJBAMaGwx5JqpB61xHgVnnb6Ax/E+B6iS2CdEd9B0qB8DvpBkwDha2bzNseX4c2O6bi08BnSH3Xph76y2w6LqOGa9ECQQDEjxp6IZwUJuieX3kBmxkbHbcydMvdm4baZF5bOz03IB4TwEsQonUw3/nlPBjEV5ewveu8e/ZS1OjLOHoJ4OXnAkEAuyt68idJ5AWsl3IGlcU6te1pdA6THpELyAqcwSZYtGMySKBZfCCTt1Kfhf8fpmvccF3v/pU+TZ7uAw+nwQyOwQJAMrg2fdNUYgLUPaGnKNnrtYqKbjOjM3DhcFM74dC8cBASksgo1ocFNHJnGlbSkT+E/e9T+BQIsMENvPq6yvfEewJAXhsqQntbRqXnCLVYo+Lc7rT+3oLj5FiPvXVGQtPAKsVHP1/ICAoHiQLvTief2PIowSJRDwsKEB4kxuC3CIBvuA==";
	public static final String DEFAULT_PUBLIC_KEY_STRING = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYbicylGw1tbDVTfqw5Nee0x6sG2CZfU7CTaJ9gvFRIkXrtgleUGzlGke5rUw5Ka5CgxoOYHeLPVABMFEA4e4HIvOFw+XtR2whESozdYesRXuxzknODqBtwwxJSqNhOWNEA5w0ZbKf16e/T5rwwXjcB4pn7aJgv2QnoyFffJ0+lwIDAQAB";

	public static String decrypt(String cipherText) throws Exception {
		return decrypt((String) null, cipherText);
	}

	public static String decrypt(String publicKeyText, String cipherText)
			throws Exception {
		PublicKey publicKey = getPublicKey(publicKeyText);

		return decrypt(publicKey, cipherText);
	}

	public static PublicKey getPublicKeyByX509(String x509File) {
		if (x509File == null || x509File.length() == 0) {
			return EncryptUtils.getPublicKey(null);
		}

		FileInputStream in = null;
		try {
			in = new FileInputStream(x509File);

			CertificateFactory factory = CertificateFactory
					.getInstance("X.509");
			Certificate cer = factory.generateCertificate(in);
			return cer.getPublicKey();
		} catch (Exception e) {
			throw new IllegalArgumentException("Failed to get public key", e);
		} finally {
			IOUtils.closeQuietly(in);
		}
	}

	public static PublicKey getPublicKey(String publicKeyText) {
		if (publicKeyText == null || publicKeyText.length() == 0) {
			publicKeyText = EncryptUtils.DEFAULT_PUBLIC_KEY_STRING;
		}

		try {
			byte[] publicKeyBytes = Base64.base64ToByteArray(publicKeyText);
			X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(
					publicKeyBytes);

			KeyFactory keyFactory = KeyFactory.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM);
			return keyFactory.generatePublic(x509KeySpec);
		} catch (Exception e) {
			throw new IllegalArgumentException("Failed to get public key", e);
		}
	}

	public static PublicKey getPublicKeyByPublicKeyFile(String publicKeyFile) {
		if (publicKeyFile == null || publicKeyFile.length() == 0) {
			return EncryptUtils.getPublicKey(null);
		}

		FileInputStream in = null;
		try {
			in = new FileInputStream(publicKeyFile);
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			int len = 0;
			byte[] b = new byte[512 / 8];
			while ((len = in.read(b)) != -1) {
				out.write(b, 0, len);
			}

			byte[] publicKeyBytes = out.toByteArray();
			X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes);
			KeyFactory factory = KeyFactory.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM);
			return factory.generatePublic(spec);
		} catch (Exception e) {
			throw new IllegalArgumentException("Failed to get public key", e);
		} finally {
			IOUtils.closeQuietly(in);
		}
	}

	public static String decrypt(PublicKey publicKey, String cipherText)
			throws Exception {
		Cipher cipher = Cipher.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM);
		try {
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
		} catch (InvalidKeyException e) {
			//for IBM JDK
			RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
			RSAPrivateKeySpec spec = new RSAPrivateKeySpec(
					rsaPublicKey.getModulus(), rsaPublicKey.getPublicExponent());
			Key fakePrivateKey = KeyFactory.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM).generatePrivate(
					spec);
			cipher = Cipher.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM); // It is a stateful object. so we need to get new one.
			cipher.init(Cipher.DECRYPT_MODE, fakePrivateKey);
		}

		if (cipherText == null || cipherText.length() == 0) {
			return cipherText;
		}

		byte[] cipherBytes = Base64.base64ToByteArray(cipherText);
		byte[] plainBytes = cipher.doFinal(cipherBytes);

		return new String(plainBytes);
	}

	public static String encrypt(String plainText) throws Exception {
		return encrypt((String) null, plainText);
	}

	public static String encrypt(String key, String plainText) throws Exception {
		if (key == null) {
			key = DEFAULT_PRIVATE_KEY_STRING;
		}

		byte[] keyBytes = Base64.base64ToByteArray(key);
		return encrypt(keyBytes, plainText);
	}

	public static String encrypt(byte[] keyBytes, String plainText)
			throws Exception {
		PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory factory = KeyFactory.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM);
		PrivateKey privateKey = factory.generatePrivate(spec);
		Cipher cipher = Cipher.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM);
		try {
			cipher.init(Cipher.ENCRYPT_MODE, privateKey);
		} catch (InvalidKeyException e) {
			// For IBM JDK
			RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
			RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(
					rsaPrivateKey.getModulus(),
					rsaPrivateKey.getPrivateExponent());
			Key fakePublicKey = KeyFactory.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM).generatePublic(
					publicKeySpec);
			cipher = Cipher.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM);
			cipher.init(Cipher.ENCRYPT_MODE, fakePublicKey);
		}

		byte[] encryptedBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
		String encryptedString = Base64.byteArrayToBase64(encryptedBytes);

		return encryptedString;
	}

	public static byte[][] genKeyPairBytes(int keySize)
			throws NoSuchAlgorithmException {
		byte[][] keyPairBytes = new byte[2][];

		KeyPairGenerator gen = KeyPairGenerator.getInstance(EncryptUtils.DEFAULT_KEY_ALGORITHM);
		gen.initialize(keySize, new SecureRandom());
		KeyPair pair = gen.generateKeyPair();

		keyPairBytes[0] = pair.getPrivate().getEncoded();
		keyPairBytes[1] = pair.getPublic().getEncoded();

		return keyPairBytes;
	}

	/**
	 * 第一个元素：私钥
	 * 第二个元素：公钥
	 * @param keySize
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public static String[] genKeyPair(int keySize)
			throws NoSuchAlgorithmException {
		byte[][] keyPairBytes = genKeyPairBytes(keySize);
		String[] keyPairs = new String[2];

		keyPairs[0] = Base64.byteArrayToBase64(keyPairBytes[0]);
		keyPairs[1] = Base64.byteArrayToBase64(keyPairBytes[1]);

		return keyPairs;
	}
	
	/**
	 * 解密系统中配置的敏感数据
	 * @param waitDecryptStr
	 * @return
	 */
	public static String decryptSensitivity(String waitDecryptStr) {
		USoftLogger logger = LogFactory.getLogger(EncryptUtils.class);
		if (!StringUtils.isNull(waitDecryptStr)) {
			if (waitDecryptStr.startsWith(Constant.ENCRYPT_PREFIX)) { //表示客户端启用加密处理
				logger.debug("数据已经加密：" + waitDecryptStr);
				String passwordEncryptStr = waitDecryptStr.substring(Constant.ENCRYPT_PREFIX.length());
				PropertiesParser pp = new PropertiesParser();
				String publicKey = pp.getStringProperty(Constant.ENCRYPT_KEY);
				logger.debug("解密公钥：" + publicKey);
				try {
					waitDecryptStr = EncryptUtils.decrypt(publicKey, passwordEncryptStr);
					logger.debug("数据解密后：" + waitDecryptStr);
				} catch (Exception e) {
					logger.error("解密出错：" + waitDecryptStr, e);
				}
			}
		}
		return waitDecryptStr;
	}
}
